/**
  ******************************************************************************
  * @author     Chris
  * @since      2024/6/24 23:22
  *
  * @file       pd_spi_sim.c
  * @brief      SPI simulate driver.
  *
  * @note       This file contains the simulate driver for the SPI.
  *
  * @warning    None.
  ******************************************************************************
  * Change Logs:
  *   Date          Author       Notes
  *   2024/6/24     Chris        the first version
  *
  ******************************************************************************
  */


#include <stm32f4xx_ll_spi.h>
#include <stm32f4xx_ll_gpio.h>
#include <stdlib.h>
#include "pd_spi_sim.h"

static uint8_t SPISimulate_readBit(GPIO *gpio);

static void SPISimulate_writeBit(GPIO *gpio, FlagStatus BitVal);


#define SCLK(x)     SPISimulate_writeBit(this->spi.sclk, x)
#define SSEL(x, y)  SPISimulate_writeBit(x, y)
#define MOSI(x)     SPISimulate_writeBit(this->spi.mosi, x)
#define MISO()      SPISimulate_readBit(this->spi.miso)

static void SPISimulate_writeBit(GPIO *gpio, FlagStatus BitVal) {
    if (gpio) gpio->GPIOx->BSRR = BitVal ? gpio->Pin : gpio->Pin << 16;
}

static uint8_t SPISimulate_readBit(GPIO *gpio) {
    if (gpio)
        return gpio->getBit(gpio);
    else
        return 0;
}


static void SPISimulate_init(SPI *super) {
    SPISimulate *this = (SPISimulate *) super;

    for (int i = 0; i < super->sselSize; ++i) {
        super->ssel[i]->init(super->ssel[i]);
    }
    if (super->sclk) super->sclk->init(super->sclk);
    if (super->mosi) super->mosi->init(super->mosi);
    if (super->miso) super->miso->init(super->miso);

    // Set all slave select to disable
    for (int i = 0; i < super->sselSize; ++i) {
        SSEL(super->ssel[i], 1);
    }

    SCLK(0);

}

static void SPISimulate_start(SPI *super, GPIO *ssel) {
    SSEL(ssel, 0);
}

static uint8_t SPISimulate_swapByte(SPI *super, uint8_t dat) {
    SPISimulate *this = (SPISimulate *) super;
    uint8_t ret = 0x00;

    for (int i = 0; i < 8; i++) {
        MOSI(dat & (0x80 >> i));
        SCLK(1);
        if (MISO() == 1) ret |= (0x80 >> i);
        SCLK(0);
    }

    return ret;
}

static void SPISimulate_stop(SPI *super, GPIO *ssel) {
    SSEL(ssel, 1);
}


static void SPISimulate_preConfigGPIO(GPIO *gpio) {
    if (!gpio) return;

    gpio->Mode = LL_GPIO_MODE_OUTPUT;
    gpio->Speed = LL_GPIO_SPEED_FREQ_VERY_HIGH;
    gpio->Pull = LL_GPIO_PULL_UP;
}

static SPISimulate *build(SPIGPIO *spiGPIO) {
    SPISimulate *spiSimulate = malloc(sizeof(SPISimulate));

    Driver_add(spiSimulate, GEN_PERM);

    // SPI GPIO pre-config
    GPIO *gpios[] = {spiGPIO->sclk, spiGPIO->mosi, spiGPIO->miso};
    for (int i = 0; i < 3; ++i) {
        SPISimulate_preConfigGPIO(gpios[i]);
    }
    for (int i = 0; i < spiGPIO->sselSize; ++i) {
        SPISimulate_preConfigGPIO(spiGPIO->ssel[i]);
    }

    spiGPIO->miso->Mode = LL_GPIO_MODE_INPUT;


    SPI *spi = SPIBuilder.build(spiGPIO->ssel, spiGPIO->sselSize, spiGPIO->sclk, spiGPIO->mosi, spiGPIO->miso);
    spiSimulate->spi          = *spi;
    spiSimulate->spi.init     = &SPISimulate_init;
    spiSimulate->spi.start    = &SPISimulate_start;
    spiSimulate->spi.swapByte = &SPISimulate_swapByte;
    spiSimulate->spi.stop     = &SPISimulate_stop;

    return spiSimulate;
}

const struct SPISimulateClass SPISimulateBuilder = {.build = &build};
